
C++17引入了一种新的推导:从变量声明的初始化式或函数表示法类型转换中，指定的参数推导类类型的模板参数。例如:

\begin{lstlisting}[style=styleCXX]
template<typename T1, typename T2, typename T3 = T2>
class C
{
	public:
	// constructor for 0, 1, 2, or 3 arguments:
	C (T1 x = T1{}, T2 y = T2{}, T3 z = T3{});
	...
};

C c1(22, 44.3, "hi"); // OK in C++17: T1 is int, T2 is double, T3 is char const*
C c2(22, 44.3); // OK in C++17: T1 is int, T2 and T3 are double
C c3("hi", "guy"); // OK in C++17: T1, T2, and T3 are char const*
C c4; // ERROR: T1 and T2 are undefined
C c5("hi"); // ERROR: T2 is undefined
\end{lstlisting}

注意，所有参数都必须由推导过程或由默认参数确定。不可能显式地指定一些参数去推断其他的参数。例如:

\begin{lstlisting}[style=styleCXX]
C<string> c10("hi","my", 42); // ERROR: only T1 explicitly specified, T2 not deduced
C<> c11(22, 44.3, 42); // ERROR: neither T1 nor T2 explicitly specified
C<string,string> c12("hi","my"); // OK: T1 and T2 are deduced, T3 has default
\end{lstlisting}

\subsubsubsection{15.12.1\hspace{0.2cm}推导指引}

考虑对15.8.2节中前面例子做的一个改动:

\begin{lstlisting}[style=styleCXX]
emplate<typename T>
class S {
	private:
	T a;
	public:
	S(T b) : a(b) {
	}
};

template<typename T> S(T) -> S<T>; // deduction guide

S x{12}; // OK since C++17, same as: S<int> x{12};
S y(12); // OK since C++17, same as: S<int> y(12);
auto z = S{12}; // OK since C++17, same as: auto z = S<int>{12};
\end{lstlisting}

添加了一个名为推导指引的类模板构造。看起来有点像函数模板，但在语法上与函数模板有一些不同:

\begin{itemize}
\item 
看起来像尾部返回类型的部分不能写成传统的返回类型，将指定的类型(示例中为S<T>)称为引导类型。

\item 
没有前置的auto关键字来指示尾部的返回类型。

\item 
推导指引的“名称”必须是前面在同一作用域中，声明的类模板的非限定名称。

\item 
指引类型必须是一个模板标识，其模板名称对应于指引名称。

\item 
可以使用显式说明符进行声明。
\end{itemize}	

声明S x(12);说明符S称为占位符类类型。

\begin{tcolorbox}[colback=webgreen!5!white,colframe=webgreen!75!black]
\hspace*{0.75cm}注意占位符类型和占位符类类型之间的区别，占位符类型是auto或decltype(auto)，可以解析为任何类型，而占位符类类型是模板名称，只能解析为指定模板的实例的类类型。
\end{tcolorbox}

当使用这样的占位符时，要声明的变量名必须立即跟在后面，然后必须跟一个初始化式。因此，以下内容无效:
	
\begin{lstlisting}[style=styleCXX]
S* p = &x; // ERROR: syntax not permitted
\end{lstlisting}

S x(12);这一声明中，通过将与类S相关联的推导指引视为重载集，并尝试使用针对该重载集的初始化式进行重载解析，从而推导出变量的类型。此时，集合中只有一个指引，可成功地推导出T为int，并且指引型为S<int>。

\begin{tcolorbox}[colback=webgreen!5!white,colframe=webgreen!75!black]
\hspace*{0.75cm}与普通函数模板推导一样，也可以使用SFINAE(若在引导类型中替换推导出的参数失败)。这个简单的例子中，情况并非如此。
\end{tcolorbox}

因此，该引导类型为声明的类型。

注意，若一个类模板名称后面有多个声明符需要推导，那么每个声明符的初始化式必须生成相同的类型:

\begin{lstlisting}[style=styleCXX]
S s1(1), s2(2.0); // ERROR: deduces S both as S<int> and S<double>
\end{lstlisting}

这类似于推导C++11占位符类型auto时的约束。

在声明的推导指引和类S中声明的构造函数S(T b)之间有一个隐式连接。但这样的连接不是必需的，推导指引也可以用于聚合类模板:

\begin{lstlisting}[style=styleCXX]
template<typename T>
struct A
{
	T val;
};

template<typename T> A(T) -> A<T>; // deduction guide
\end{lstlisting}

若没有推导指引，需要(甚至在C++17中)显式指定模板参数:

\begin{lstlisting}[style=styleCXX]
A<int> a1{42}; // OK
A<int> a2(42); // ERROR: not aggregate initialization
A<int> a3 = {42}; // OK
A a4 = 42; // ERROR: can’t deduce type
\end{lstlisting}

但根据上面的指引，可以这样写:

\begin{lstlisting}[style=styleCXX]
A a4 = { 42 }; // OK
\end{lstlisting}

这类情况的微妙之处在于，初始化式必须是一个有效的聚合初始化式，必须使用带大括号的初始化列表。因此，以下方式非法:

\begin{lstlisting}[style=styleCXX]
A a5(42); // ERROR: not aggregate initialization
A a6 = 42; // ERROR: not aggregate initialization
\end{lstlisting}

\subsubsubsection{15.12.2\hspace{0.2cm}隐式推导指引}

通常，类模板中的每个构造函数都需要推导指引。这导致类模板参数推导的设计者为推导设计了一种隐式机制。这相当于为主类模板的每个构造函数和构造函数模板引入一个隐式推导指引，如下所示:

\begin{tcolorbox}[colback=webgreen!5!white,colframe=webgreen!75!black]
\hspace*{0.75cm}第16章介绍了以各种方式“特化”类模板的能力。此类特化不参与类模板参数推导。
\end{tcolorbox}

\begin{itemize}
\item 
隐式知道的模板参数列表包括类模板的模板参数，在构造函数模板的情况下，后面是构造函数模板的模板参数。构造函数模板的模板参数保留默认参数。

\item 
指引中的“类函数”参数从构造函数或构造函数模板中复制。

\item 
指引类型是模板的名称，参数是从类模板中获取的模板参数。
\end{itemize}	

应用到原始的简单类模板上:

\begin{lstlisting}[style=styleCXX]
template<typename T>
class S {
	private:
	T a;
	public:
	S(T b) : a(b) {
	}
};
\end{lstlisting}

模板参数列表是typename T，类函数参数列表变成(T b)，所以引导类型是S<T>。因此，得到了一个与之前编写的用户声明指引等价的指引类型:该指引不需要达到我们预期的效果!只使用最初编写的简单类模板(没有推导指引)，就可以有效地编写S x(12);预期的结果是x为S<int>。

推导指引有一种模糊性。再次考虑简单类模板S和以下初始化:

\begin{lstlisting}[style=styleCXX]
S x{12}; // x has type S<int>
S y{s1};
S z(s1);
\end{lstlisting}

已经知道x的类型是S<int>，但是x和y的类型应该是什么?直观产生的两种类型是S<S<int>{}>和S<int>。委员会决定，两种情况下都应该是S<int>，这有点争议。为什么会引起争议?考虑一个类似的vector类型的例子:

\begin{lstlisting}[style=styleCXX]
std::vector v{1, 2, 3}; // vector<int>, not surprising
std::vector w2{v, v}; // vector<vector<int>>
std::vector w1{v}; // vector<int>!
\end{lstlisting}

换句话说，一个和多个元素带大括号的初始化式的初始化推导不同。通常情况下，一个元素的结果是所希望的，但二者确实不同。然而，在泛型代码中，很容易忽略以下差别:

\begin{lstlisting}[style=styleCXX]
template<typename T, typename... Ts>
auto f(T p, Ts... ps) {
	std::vector v{p, ps...}; // type depends on pack length
	...
}
\end{lstlisting}

若T推导为一个vector类型，那么v的类型将根据ps是空包或非空包而推导出不同的类型。

隐式模板指引本身的添加并非没有争议。反对者的主要论点是，该特性会自动向现有库添加接口。要理解这一点，再次考虑上面简单的类模板S。自模板在C++中引入以来，定义一直有效。但假设S的作者扩充了库，使得S的定义更加精细:

\begin{lstlisting}[style=styleCXX]
template<typename T>
struct ValueArg {
	using Type = T;
};

template<typename T>
class S {
	private:
	T a;
	public:
	using ArgType = typename ValueArg<T>::Type;
	S(ArgType b) : a(b) {
	}
};
\end{lstlisting}

C++17前，这样的转换(并不少见)不会影响现有的代码。然而，在C++17中禁用了隐式推导指引。为了了解这一点，编写一个对应于上述隐式推导指引构造过程产生的推导指引:模板参数列表和导向类型不变，但类函数参数现在是ArgType，为typename ValueArg<T>::Type:

\begin{lstlisting}[style=styleCXX]
template<typename> S(typename ValueArg<T>::Type) -> S<T>;
\end{lstlisting}

回想一下15.2节，像ValueArg<T>::这样的名称限定符不能进行上下文的推导。因此，这种形式的推导指引无效，不能解析像S x(12);这样的声明。换句话说，库编写者执行这种转换很可能会破坏C++17的代码。

库作者该怎么做?建议仔细考虑每个构造函数是否希望在库的生命周期中，将其作为隐式推导指引的来源。如果不是，则用typename ValueArg<X>::Type替换类型为X(可推导构造函数参数)的每个实例。但是，没有更简单的方法可以“退出”隐式推导指引。

\subsubsubsection{15.12.3\hspace{0.2cm}其他要点}

\hspace*{\fill} \\ %插入空行
\noindent
\textbf{注入类名}

考虑下面的例子:

\begin{lstlisting}[style=styleCXX]
template<typename T> struct X {
	template<typename Iter> X(Iter b, Iter e);
	template<typename Iter> auto f(Iter b, Iter e) {
		return X(b, e); // What is this?
	}
};
\end{lstlisting}

这段代码在C++ 4中是有效的:X(b, e)中的X是注入类名，在这个上下文中等价于X<T>(参见第13.2.3节)。然而，类模板参数推导的规则会使X等价于X<iter>。

为了保持向后兼容性，若模板的名称是注入类名，则禁用类模板参数进行推导。


\hspace*{\fill} \\ %插入空行
\noindent
\textbf{转发引用}

考虑下面的例子:

\begin{lstlisting}[style=styleCXX]
template<typename T> struct Y {
	Y(T const&);
	Y(T&&);
};
void g(std::string s) {
	Y y = s;
}
\end{lstlisting}

这里的目的是通过与复制构造函数关联的隐式推导指引，推断T为std::string。然而，将隐式推导指引写成显式声明的指引则会令人吃惊:

\begin{lstlisting}[style=styleCXX]
template<typename T> Y(T const&) -> Y<T>; // #1
template<typename T> Y(T&&) -> Y<T>; // #2
\end{lstlisting}

回想一下第15.6节中，T\&\&的行为在模板参数推导期间的特殊行为:作为转发引用，若对应的参数是左值，会导致T推导为引用类型。上面的例子中，推导过程中的参数是表达式s，是一个左值。隐式指引\#1推导T是std::string，但要求将参数从std::string调整为std::string const。然而，指引\#2通常会推断T是一个引用类型std::string\&，并产生相同类型的参数(引用折叠规则)，这是一个更好的匹配，不需要添加const来进行类型调整。

这个结果将相当令人惊讶，并且可能会导致实例化错误(不允许引用类型的上下文中使用类模板参数)，或者更糟糕的是，会静默产生行为错误的实例化(产生悬空引用)。

因此，C++标准化委员会决定在隐式推导指引执行推导时，禁用T的特殊推导指引。若T最初是一个类模板参数(与构造函数模板参数相反;对于这些，特殊的推导规则仍然存在)，如上例推断T为std::string那样。

\hspace*{\fill} \\ %插入空行
\noindent
\textbf{explicit关键字}

推导指引可以用关键字explicit声明。然后，只在直接初始化的情况下使用:

\begin{lstlisting}[style=styleCXX]
template<typename T, typename U> struct Z {
	Z(T const&);
	Z(T&&);
};

template<typename T> Z(T const&) -> Z<T, T&>; // #1
template<typename T> explicit Z(T&&) -> Z<T, T>; // #2

Z z1 = 1; // only considers #1 ; same as: Z<int, int&> z1 = 1;
Z z2{2}; // prefers #2 ; same as: Z<int, int> z2{2};
\end{lstlisting}

注意，z1的初始化是复制初始化。因为它是显式声明的，所以推导指引\#2没用到。

\hspace*{\fill} \\ %插入空行
\noindent
\textbf{复制构造和初始化列表}

考虑下面的类模板:

\begin{lstlisting}[style=styleCXX]
template<typename ... Ts> struct Tuple {
	Tuple(Ts...);
	Tuple(Tuple<Ts...> const&);
};
\end{lstlisting}

为了理解隐式指引的作用，把它们写成显式声明:

\begin{lstlisting}[style=styleCXX]
template<typename... Ts> Tuple(Ts...) -> Tuple<Ts...>;
template<typename... Ts> Tuple(Tuple<Ts...> const&) -> Tuple<Ts...>;
\end{lstlisting}

现在来看一些例子:

\begin{lstlisting}[style=styleCXX]
auto x = Tuple{1,2};
\end{lstlisting}

这显然选择了第一个指引，也选择了第一个构造函数:因此x是一个Tuple<int, int>类型。继续看一些使用复制x的例子:

\begin{lstlisting}[style=styleCXX]
Tuple a = x;
Tuple b(x);
\end{lstlisting}

对于a和b，两个指引都匹配。第一个指引选择类型Tuple<tuple<int, int>，而与复制构造函数相关联的指引生成Tuple<int, int>。幸运的是，第二个指引匹配得更好，因此a和b都从x复制构建。

现在，考虑一些使用带大括号初始化列表的例子:

\begin{lstlisting}[style=styleCXX]
Tuple c{x, x};
Tuple d{x};
\end{lstlisting}

第一个示例(x)只能匹配第一个指引，因此产生Tuple<Tuple<int, int>，Tuple<int, int>{}>。这表明，第二个示例应该推导d为Tuple<tuple<int>{}>类型。不过，其使用了复制的方式进行构造(首选第二个隐式指引)。这也发生在函数功能符类型的转换中:

\begin{lstlisting}[style=styleCXX]
auto e = Tuple{x};
\end{lstlisting}

这里，e推导为一个Tuple<int, int>，而不是Tuple<Tuple<int>{}>。

\hspace*{\fill} \\ %插入空行
\noindent
\textbf{指引仅为推导所用}

推导指引不是函数模板:只用于演绎模板参数，不可“调用”。通过引用传递参数和通过值传递参数之间的差异，对于指引声明并不重要。例如:

\begin{lstlisting}[style=styleCXX]
template<typename T> struct X {
	...
};

template<typename T> struct Y {
	Y(X<T> const&);
	Y(X<T>&&);
};

template<typename T> Y(X<T>) -> Y<T>;
\end{lstlisting}

请注意推导指引与y的两个构造函数并不完全对应。这并不重要，因为该指引只用于推导。给定值xtt的类型为X<TT>——lvalue或rvalue——将选择推导出的类型Y<TT>。然后，初始化将对Y<TT>的构造函数执行重载解析，以决定调用哪一个(这将取决于xtt是左值还是右值)。





